package dao

import (
	"context"
	"gorm.io/gorm"
	"time"
)

type JobDao interface {
	Preempt(ctx context.Context) (Job, error)
	Release(ctx context.Context, id int64) error
	UpdateUtime(ctx context.Context, id int64) error
	UpdateNextTime(ctx context.Context, id int64) error
	Stop(ctx context.Context, id int64) error
	Insert(ctx context.Context, job Job) error
}
type GORMJobDao struct {
	db *gorm.DB
}

func (g *GORMJobDao) Preempt(ctx context.Context) (Job, error) {
	for {
		now := time.Now()
		var j Job
		err := g.db.WithContext(ctx).Where("status=? and next_time<=?", jobStatusWaiting,
			now).First(&j).Error
		// 你找到可以被抢占的
		if err != nil {
			//没有任务，返回
			return Job{}, err
		}

		// 更新在3分钟前，认为续约失败
		ddl := now.Add(-time.Minute * 3).UnixMilli()
		//面试：用乐观锁取代for update
		//性能优化：用for update=>性能差，有死锁=>优化乐观锁
		res := g.db.WithContext(ctx).Model(&j).Where("(id=? and version=?) or (status=? and utime<?)",
			j.Id, j.Version, jobStatusRunning, ddl).
			Updates(map[string]any{
				"status":  jobStatusRunning,
				"utime":   now,
				"version": j.Version + 1,
			})
		if res.Error != nil {
			return Job{}, res.Error
		}
		if res.RowsAffected == 0 {
			//抢占失败,只能说，我要继续下一轮
			continue
		}
		return j, nil
	}
}
func (g *GORMJobDao) Release(ctx context.Context, id int64) error {
	var j Job
	err := g.db.WithContext(ctx).Where("id=?", id).First(&j).Error
	// 你找到可以被抢占的
	if err != nil {
		//没有任务，返回
		return err
	}
	return g.db.WithContext(ctx).Model(&j).Where("id=? and version=?", id, j.Version).
		Updates(map[string]any{
			"status":  jobStatusWaiting,
			"utime":   time.Now().UnixMilli(),
			"version": j.Version + 1,
		}).Error
}
func (g *GORMJobDao) UpdateUtime(ctx context.Context, id int64) error {
	return g.db.WithContext(ctx).Model(&Job{}).Where("id=?", id).
		Updates(map[string]any{
			"utime": time.Now().UnixMilli(),
		}).Error
}
func (g *GORMJobDao) UpdateNextTime(ctx context.Context, id int64) error {
	return g.db.WithContext(ctx).Model(&Job{}).Where("id=?", id).
		Updates(map[string]any{
			"next_time": time.Now().UnixMilli(),
		}).Error
}
func (g *GORMJobDao) Stop(ctx context.Context, id int64) error {
	var j Job
	err := g.db.WithContext(ctx).Where("id=?", id).First(&j).Error
	// 你找到可以被抢占的
	if err != nil {
		//没有任务，返回
		return err
	}
	return g.db.WithContext(ctx).Model(&j).Where("id=? and version=?", id, j.Version).
		Updates(map[string]any{
			"status":  jobStatusPaused,
			"utime":   time.Now().UnixMilli(),
			"version": j.Version + 1,
		}).Error
}
func (g *GORMJobDao) Insert(ctx context.Context, job Job) error {
	return g.db.WithContext(ctx).Create(&job).Error
}

const (
	jobStatusUnknown = iota
	jobStatusWaiting
	//已经被抢占
	jobStatusRunning
	// 不再需要调度了，比如说被终止了，或者被删除了。
	// 我们这里没有严格区分这两种情况的必要性
	jobStatusPaused
)

type Job struct {
	Id         int64  `gorm:"primaryKey,autoIncrement"`
	Name       string `gorm:"index"`
	Executor   string
	Cfg        string
	Expression string
	Version    int64
	//下一次调度的时间
	NextTime int64 `gorm:"index"`
	//哪些任务可以抢，哪些任务已经被人占着
	Status int
	Ctime  int64
	Utime  int64
}

func (Job) TableName() string {
	return "job"
}
